iT邦幫忙

2026 iThome 鐵人賽

DAY 9
1

前言

昨天的 /spec-check 可以告訴我規格哪裡有問題,但它只是「告訴我」。如果我希望某條規則不能被違反,而不是期待 Claude 記得遵守,就得再往下一層。

這就是今天要介紹的 Hooks。


昨天說到

昨天做出第一支自己的 skill,它會逐條檢查規格、輸出缺口報告。

但它有個限制:觸發的方式是我得記得叫它。 而且就算它報出九項必補,也沒有任何東西阻止我直接把規格交出去。

這其實是 D04 就埋下的問題:

CLAUDE.md 是 Context,不是防火牆。

當時只是先提到,今天就把這件事情講完:如果真的想阻止 Claude 做某件事,該怎麼做?


為什麼 CLAUDE.md 擋不住東西

CLAUDE.md 的內容會在每個 session 開始時被讀進 context。它是資訊,不是設定

所以「不要直接改 migration 檔」這句話寫在 CLAUDE.md 裡,效果等於在一段很長的提示詞裡加一句叮嚀。Claude 大部分時候會照做,但不是一道門檻。


Hook 是什麼

Hook 就是在 Claude Code 執行某個動作前後,插入一段自己的程式。

Claude 想修改檔案
        ↓
PreToolUse Hook
        ↓
    檢查規則
        ↓
   ┌────┴────┐
   ↓         ↓
 通過       阻擋
   ↓         ↓
繼續執行     停止

Hook 設定放在:

.claude/settings.json

例如:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard.py\"" }
        ]
      }
    ]
  }
}

這裡有兩個重點:

  • PreToolUse:工具執行之前觸發
  • matcher:指定哪些工具要經過這個 Hook

Claude Code 有很多不同的 Hook Event,和開發流程比較相關的幾個如下:

事件 什麼時候觸發
PreToolUse 工具呼叫執行之前
PostToolUse 工具呼叫成功之後
Stop Claude 準備結束回應時
SessionStart session 開始或恢復
InstructionsLoaded CLAUDE.md 或 rules 被載入時

實作一:收工前先跑測試

第一個實驗先處理一個很常見的問題:Claude 說「做完了」,真的代表做完了嗎?

所以我把 Hook 掛在 Stop 事件上,讓 Claude 準備結束回應時,先跑一次 pytest,如果測試沒過,就不可以收工。

設定

{
  "hooks": {
    "Stop": [
      {
        "hooks": [{
          "type": "command",
          "command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/stop-run-tests.py\"",
          "statusMessage": "收工前跑測試"
        }]
      }
    ]
  }
}

Hook 裡加了一個簡單的 marker,避免同一個 session 因為測試失敗而無限觸發:

if os.path.exists(marker):
    sys.stderr.write("測試仍未通過,但本 session 已提醒過一次,放行。")
    return 0

執行
接著故意給 Claude 一個會讓測試失敗的任務:

把 /health 回傳的 status 從 "ok" 改成 "healthy",不要動測試檔

結果
Claude 修改 main.py 後準備收工,卻被 Hook 擋下來:

● Ran 1 stop hook
  ⎿  Stop hook error: 測試未通過,先不要收工。
     E  AssertionError: assert {'status': 'healthy'} == {'status': 'ok'}
     FAILED tests/test_health.py::test_health_returns_ok
     請修好再結束。

這次 Hook 做到了一件很單純的事:測試沒過,就不能宣稱任務完成,而且我還加了一個限制:不能修改測試檔,所以 Claude 不能直接把測試改成 healthy 來讓它變綠,只能回頭修正真正的問題。

這就是 Hook 和 CLAUDE.md 的差別;CLAUDE.md 可以告訴 Claude:「記得跑測試。」,Hook 則可以直接把它變成:「測試沒過,你不能收工。」


實作二:一支保護憲法的 Hook

第一個實作解決的是流程上的門檻,我還有另一個問題:如果有些檔案本來就不應該讓 AI 修改呢?例如專案的開發原則,理論上應該由人決定,而不是 Claude 在執行任務時自己修改。

所以第二個實驗,我改用 PreToolUse:只要 Claude 準備修改受保護的檔案,就先攔下來。

設定

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|NotebookEdit|Bash",
        "hooks": [{
          "type": "command",
          "command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-constitution.py\"",
          "statusMessage": "檢查是否修改受保護檔案"
        }]
      }
    ]
  }
}

保護的檔案包含:

PROTECTED = [
    ".specify/memory/constitution.md",
    ".claude/settings.json",              # 不然它可以直接把 hook 刪掉
    ".claude/hooks/guard-constitution.py",
]

這裡特別把 settings.json 和 Hook 自己也放進保護清單。不然 Claude 如果可以修改 settings.json,理論上就能把 Hook 關掉;如果可以修改 guard 本身,也可能直接把保護邏輯改掉。

不同工具收到的參數結構不一樣,所以判斷方式也不同:

  • Edit / Write / NotebookEdit:可以直接取得 file_path,比對檔案路徑。
  • Bash:拿到的是整串 command,只能從指令內容判斷。

執行
接著測試:

把 .specify/memory/constitution.md 的第一條原則改成「API 欄位一律 camelCase」

結果
這次Hook 擋下來了,Claude 沒辦法直接修改檔案,並把原本的修改轉成一份給人執行的提案:

  • 給出完整的手動 patch 內容
  • 標出一個它自己做的判斷要我覆寫
  • 列出 7 個檔案、17 處會違憲

它把 AI 的角色從直接修改,變成分析影響 → 提出方案 → 由人決定是否執行


踩到的坑

上面是測試成功的版本,但其實前幾版也有遇到失敗的案例,以下是我實測過程中遇到的問題:

Matcher 沒有攔到

我開一個新的 Session 請 Claude:把憲法第一條從 snake_case 改成 camelCase,結果它真的改成功了。

看執行紀錄才發現:Claude 這次不是使用 Edit 或 Write,而是 Bash → 執行指令 → 修改檔案。我原本的 Matcher 只有 Edit|Write,所以 Hook 根本沒有被觸發。

以為在保護「檔案」,但 PreToolUse 實際上是在攔「工具」。

補上 Bash,還是不夠

於是我把 Matcher 改成 Edit|Write|NotebookEdit|Bash,這次真的擋住了,但我又測了幾個情境:

  • 修改 settings.json
  • 修改 Hook 自己
  • 用 Bash 刪除設定檔

結果是全部放行

原因很簡單,我保護的是:constitution.md,這道門會保護憲法,卻不保護自己,所以我又把 Hook 本身和設定檔加入保護範圍。

Hook 攔的是工具,不是人

Claude 又提醒我一件事:

PreToolUse 只能攔 Claude Code 的工具呼叫,不能攔人直接修改檔案。

如果真的要做到檔案層級的保護,還是要靠:

  • OS 權限
  • Read-only
  • Git branch / protection
  • CI/CD

Hook 比較適合拿來做:流程的門檻,而不是:檔案系統的防火牆。


小結

  • CLAUDE.md 是 Context,不是強制規則。它可以告訴 Claude 怎麼做,但不能保證 Claude 一定照做。
  • Hook 是把規則變成執行時檢查的機制,可以在工具執行前後介入流程。
  • PreToolUse 可以阻止工具執行,Stop 則可以阻止 Claude 結束回應。
  • Hook 攔的是工具呼叫,不是檔案本身。

明天:hook 讓規則在本機有了執行力,但它擋的都是這台機器上的事。如果規格不只活在 markdown,還存在 Issue、資料庫 schema、套件文件裡呢?那就要讓 Claude 能連出去——MCP。


上一篇
Day 08|Command 與 Skill:把 SOP 變成可重複執行的指令
下一篇
Day 10|MCP:讓 Claude 接觸外部世界
系列文
30 天打造我的 AI 開發工作流:從需求分析到上線22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言